package health_system.health_system.websocket;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.SpringConfigurator;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint(value = "/VideoConferenceServer", configurator = SpringConfigurator.class)
public class VideoConferenceServer{
    private static final Map<String,Map<String,Session>>roomMap=new ConcurrentHashMap<>();
    private static final Map<Session,UserContext>sessionUserMap=new ConcurrentHashMap<>();
    private static class UserContext{
        String roomId;
        String username;
        UserContext(String roomId,String username){this.roomId=roomId;this.username=username;}}
    @OnOpen
    public void onOpen(Session session){
        Map<String,String>params=parseQuery(session.getQueryString());
        String roomId=params.get("roomId");
        String username=params.get("username");
        if(roomId==null||username==null){closeSession(session,"Missing parameters");return;}
        synchronized(roomMap){
            Map<String,Session>participants=roomMap.get(roomId);
            if(participants==null){
                participants=new ConcurrentHashMap<>();
                roomMap.put(roomId,participants);}
            else if(participants.containsKey(username)){
                closeSession(session,"Username exists");return;}
            participants.put(username,session);}
        sessionUserMap.put(session,new UserContext(roomId,username));
        broadcastSystemMessage(roomId,username,"user_join",username+" joined");}
    @OnClose
    public void onClose(Session session){
        UserContext ctx=sessionUserMap.get(session);
        if(ctx==null)return;
        String roomId=ctx.roomId;
        String username=ctx.username;
        synchronized(roomMap){
            Map<String,Session>participants=roomMap.get(roomId);
            if(participants!=null){
                participants.remove(username);
                if(participants.isEmpty()){
                    roomMap.remove(roomId);}
                else{
                    broadcastSystemMessage(roomId,username,"user_leave",username+" left");}}}
        sessionUserMap.remove(session);}
    @OnMessage
    public void onMessage(String message,Session session){
        try{
            ObjectMapper mapper=new ObjectMapper();
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
            Map<String,Object>data=mapper.readValue(message,HashMap.class);
            String type=(String)data.get("type");
            UserContext ctx=sessionUserMap.get(session);
            if(ctx==null)return;
            String roomId=ctx.roomId;
            String fromUser=ctx.username;
            Map<String,Session>participants=roomMap.get(roomId);
            if(participants==null)return;
            switch(type){
                case"offer":
                case"answer":
                case"ice_candidate":
                    handlePeerMessage(data,participants,fromUser,mapper);
                    break;
                case"chat":
                    broadcastChatMessage(participants,fromUser,data,mapper);
                    break;
                case"participants_list":
                    sendParticipantsList(session,participants,mapper);
                    break;}}catch(Exception e){e.printStackTrace();}}
    private void handlePeerMessage(Map<String,Object>data,Map<String,Session>participants,String fromUser,ObjectMapper mapper)throws Exception{
        String toUser=(String)data.get("toUser");
        Session target=participants.get(toUser);
        if(target==null)return;
        Map<String,Object>relay=new HashMap<>(data);
        relay.put("fromUser",fromUser);
        send(target,mapper.writeValueAsString(relay));
    }
    private void broadcastChatMessage(Map<String,Session>participants,String fromUser,Map<String,Object>data,ObjectMapper mapper)throws Exception{
        String msg=(String)data.get("msg");
        Map<String,Object>chatMsg=new HashMap<>();
        chatMsg.put("type","chat");
        chatMsg.put("fromUser",fromUser);
        chatMsg.put("msg",msg);
        String jsonMsg=mapper.writeValueAsString(chatMsg);
        participants.forEach((name,session)->{
            if(!name.equals(fromUser)){send(session,jsonMsg);}});}
    private void sendParticipantsList(Session requester,Map<String,Session>participants,ObjectMapper mapper)throws Exception{
        List<String>names=new ArrayList<>(participants.keySet());
        Map<String,Object>response=new HashMap<>();
        response.put("type","participants_list");
        response.put("users",names);
        send(requester,mapper.writeValueAsString(response));}
    private void broadcastSystemMessage(String roomId,String fromUser,String type,String msg){
        Map<String,Session>participants=roomMap.get(roomId);
        if(participants==null)return;
        try{
            ObjectMapper mapper=new ObjectMapper();
            Map<String,Object>sysMsg=new HashMap<>();
            sysMsg.put("type",type);
            sysMsg.put("msg",msg);
            sysMsg.put("fromUser",fromUser);
            sysMsg.put("timestamp",new Date().getTime());
            String json=mapper.writeValueAsString(sysMsg);
            participants.forEach((name,session)->{
                if(!name.equals(fromUser)){send(session,json);}});}catch(Exception e){e.printStackTrace();}}
    private static Map<String,String>parseQuery(String query){
        Map<String,String>params=new HashMap<>();
        if(query!=null){
            for(String pair:query.split("&")){
                int idx=pair.indexOf("=");
                if(idx>0){
                    params.put(pair.substring(0,idx),pair.substring(idx+1));}}}
        return params;}
    private void closeSession(Session session,String reason){
        try{session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT,reason));}
        catch(IOException e){e.printStackTrace();}}
    private void send(Session session,String message){
        try{
            if(session.isOpen()){
                session.getBasicRemote().sendText(message);
            }
        }
        catch(IOException e){e.printStackTrace();
        }
    }
    @OnError
    public void onError(Session session,Throwable error){
        error.printStackTrace();
    }
}